/**
 *
 */
package com.kjcxlab.avcviewer.video;

import com.kjcxlab.avcviewer.network.UdpReceiver;
import com.kjcxlab.avcviewer.player.IntegrateListener;
import com.kjcxlab.avcviewer.util.Constants;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.concurrent.ArrayBlockingQueue;

/**
 * @author efhkklm
 */
public class IntegrateH264 implements IntegrateListener {

    private final static byte[] STARTCODE = {0x00, 0x00, 0x00, 0x01};
    private ByteArrayOutputStream mFrameStream = null;
    private DataOutputStream mStream = null;
    private UdpReceiver mUdpReceiver = null;
    private H264RtpPacket mH264RtpPacket = null;
    private ArrayBlockingQueue<byte[]> inQueue = null;

    private int packageType = 0;
    private int packageLength = 0;
    private int lSeq = -1;  //last seq
    private int cSeq = -1; // current seq
    private boolean lostPacket = false;
    boolean isOneFrame = false;

    public IntegrateH264(int localPort, ArrayBlockingQueue<byte[]> queue) {
        mFrameStream = new ByteArrayOutputStream(1024 * 8);
        mStream = new DataOutputStream(mFrameStream);
        mH264RtpPacket = new H264RtpPacket();
        this.inQueue = queue;
        mUdpReceiver = new UdpReceiver(localPort, this);

    }

    public void start() {
        mUdpReceiver.start();
    }

    private final void writeStartCode() {
        mFrameStream.reset();
        try {
            mStream.write(STARTCODE);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private final void writeFirstFuA(byte[] fua) throws IOException {
        writeStartCode();
        mStream.write(fua);
    }

    private final void writeFua(byte[] fua) throws IOException {
        mStream.write(fua, 1, packageLength - 1);
    }

    // FIXME:A bug may be in this section
    public byte[] getH264Frame(byte[] data) {

        try {
            if (!mH264RtpPacket.setPacket(data)) {
                // payload is empty
                return null;
            }
            packageType = mH264RtpPacket.getPackageType();
            packageLength = mH264RtpPacket.getPayloadLength();
            if (!(packageLength > 0)) {
                return null;
            }
            byte[] temp = new byte[packageLength];
            System.arraycopy(mH264RtpPacket.getPayload(), 0, temp, 0, packageLength);
            lSeq = cSeq;
            cSeq = mH264RtpPacket.getSequenceNumber();
            //get a single packet and return
            if (packageType == Constants.RTP_PACKAGE_SINGEL) {
                writeStartCode();
                mStream.write(temp);
                isOneFrame = true;
            } else if (packageType == Constants.RTP_PACKAGE_FU_A) {
                byte fuaIndex = mH264RtpPacket.getFuIndex();
                if (fuaIndex == 3) {
                    System.out.println("error");
                    isOneFrame = false;
                }
                //check packet seqnum
                lostPacket = (cSeq - lSeq != 1);
                if (cSeq == 0 && lSeq == 65535) {
                    lostPacket = false;
                }
                //At least one packet lost,discard packet until next i frame
                if (lostPacket) {
                    if (mH264RtpPacket.isIframe()) {
                        lostPacket = false;     /*got "i" frame and clear lost flag*/
                    } else {
                        System.out.println("lost packet:" + lSeq + "/" + cSeq);
                        isOneFrame = false;
                    }
                }
                //may get "i" frame
                if (!lostPacket) {
                    // write Nal data and Nal header
                    if (fuaIndex == mH264RtpPacket.FIRST_FU_A_PACKAGE) {
                        writeFirstFuA(temp);
                    } else if (fuaIndex == mH264RtpPacket.LAST_FU_A_PACKAGE) {
                        // skip Nal header,just write Nal data
                        writeFua(temp);
                        isOneFrame = true;
                    } else {
                        writeFua(temp);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        //get a complete frame and return
        if (isOneFrame && mFrameStream.toByteArray() != null) {
            isOneFrame = false;
            return mFrameStream.toByteArray();
        }
        return null;
    }

    @Override
    public void integrateOneFrame(byte[] data) {
        byte[] pointer = getH264Frame(data);
        if (pointer != null) {
            try {
                inQueue.put(pointer);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
